package com.spring.framwork.spring;

import com.spring.framwork.spring.enty.BeanDefinition;
import com.spring.framwork.spring.annotation.BeanPostProcessor;
import com.spring.framwork.spring.annotation.Component;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ApplicationContext {
    private Map<String,Object> ioc=new HashMap<>();
    private Map<String, BeanDefinition> beanDefinitionMap=new HashMap<>();
    private Map<String,Object> loadingIoc=new HashMap<>();
    private List<BeanPostProcessor> postProcessors=new ArrayList<>();

    public ApplicationContext(String packeName) throws Exception {
        initContext(packeName);
    }
    public void initContext(String packeName)throws Exception{
        ScanPackage(packeName).stream().filter(this::scanCreate).forEach(this::wrapper);
        initBeanPostProcessor();
        beanDefinitionMap.values().forEach(this::createBean);
    }

    private void initBeanPostProcessor() {
        beanDefinitionMap.values().stream()
                .filter(bd->BeanPostProcessor.class.isAssignableFrom(bd.getBeanType()))
                .map(this::createBean)
                .map(bean->(BeanPostProcessor) bean)
                .forEach(postProcessors::add);
    }

    private List<Class<?>> ScanPackage(String packeName) throws IOException {
        //所有的class类
        List<Class<?>> classList=new ArrayList<>();
        String finalPackeName = packeName;
        packeName=packeName.replace(".","/");
        URL resource = this.getClass().getClassLoader().getResource(packeName);
        Path path = Path.of(resource.getFile().substring(1));
        //监视器模式遍历
        Files.walkFileTree(path,new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path absolutePath = file.toAbsolutePath();
                if (absolutePath.toString().endsWith(".class")){
                    String replaceStr = absolutePath.toString().replace("\\", ".");
                    int i = replaceStr.indexOf(finalPackeName);
                    String className = replaceStr.substring(i, replaceStr.length() - ".class".length());
                    try {
                       classList.add(Class.forName(className));
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
        return classList;
    }
    protected boolean scanCreate(Class<?> type){
        return type.isAnnotationPresent(Component.class);
    }
    protected BeanDefinition wrapper(Class<?> type){
        BeanDefinition beanDefinition = new BeanDefinition(type);
        if (beanDefinitionMap.containsKey(beanDefinition.getName())) {
            throw new RuntimeException("bean名字重复");
        }
        beanDefinitionMap.put(beanDefinition.getName(),beanDefinition);
        return beanDefinition;
    }
    protected Object createBean(BeanDefinition beanDefinition) {
        String name = beanDefinition.getName();
        if (ioc.containsKey(name)){
            return ioc.get(name);
        }
        if (loadingIoc.containsKey(name)){
            return loadingIoc.get(name);
        }
        return doCreateBean(beanDefinition);

    }

    private Object doCreateBean(BeanDefinition beanDefinition)  {
        Constructor<?> constructor = beanDefinition.getConstructor();
        Object bean = null;
        try {
            bean = constructor.newInstance();
            loadingIoc.put(beanDefinition.getName(),bean);
            autoWriedBean(bean,beanDefinition);
            //初始化后这个bean做操作
            bean=initalizeBean(bean,beanDefinition);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        loadingIoc.remove(beanDefinition.getName());
        ioc.put(beanDefinition.getName(),bean);
        return bean;
    }

    private Object initalizeBean(Object bean, BeanDefinition beanDefinition) throws InvocationTargetException, IllegalAccessException {
        for (BeanPostProcessor postProcessor : postProcessors) {
            bean=postProcessor.beforeInitializeBean(bean,beanDefinition.getName());
        }
        Method postConstructor = beanDefinition.getPostConstructor();
        if (postConstructor!=null){
            postConstructor.invoke(bean);
        }
        for (BeanPostProcessor postProcessor : postProcessors) {
            bean =postProcessor.afterInitializeBean(bean,beanDefinition.getName());
        }
        return bean;
    }

    private void autoWriedBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {
        for (Field autowiredField : beanDefinition.getAutowiredFields()) {
            autowiredField.setAccessible(true);
            Object autpwiredBean=null;
            Class<?> type = autowiredField.getType();
            autowiredField.set(bean,getBean(type));
        }
    }

    public Object getBean(String name){
        if (name.isEmpty()){
            return null;
        }
        Object bean = this.ioc.get(name);
        if (bean!=null){
            return bean;
        }
        if (beanDefinitionMap.containsKey(name)){
            return createBean(beanDefinitionMap.get(name));
        }
        return null;


    }
    public <T>T getBean(Class<T> beanTyoe){
        String beanName = this.beanDefinitionMap.values().stream()
                .filter(bean -> beanTyoe.isAssignableFrom(bean.getBeanType()))
                .map(BeanDefinition::getName)
                .findFirst()
                .orElse(null);
        return (T) getBean(beanName);


    }
    public <T> List<T> getBeans(Class<T> beanType){
        return this.beanDefinitionMap.values().stream()
                .filter(bean -> beanType.isAssignableFrom(bean.getBeanType()))
                .map(BeanDefinition::getName)
                .map(this::getBean)
                .map((bean)->(T) bean)
                .toList();
    }
}
